package com.xunk.equipment.service.zhijia.service;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xunk.common.core.constant.HttpStatus;
import com.xunk.common.core.enums.SupplierEnum;
import com.xunk.common.core.exception.CustomException;
import com.xunk.common.core.text.UUID;
import com.xunk.common.core.utils.DataUtils;
import com.xunk.common.core.utils.HttpUtil;
import com.xunk.common.core.utils.IdUtils;
import com.xunk.common.core.utils.StringUtils;
import com.xunk.common.core.utils.serialization.JsonSerializer;
import com.xunk.common.core.web.domain.AjaxResult;
import com.xunk.common.redis.service.RedisService;
import com.xunk.common.security.utils.SecurityUtils;
import com.xunk.equipment.config.ZhijiaConfig;
import com.xunk.equipment.domain.BindExternToken;
import com.xunk.equipment.domain.EquipmentBaseInfoDO;
import com.xunk.equipment.domain.EquipmentDO;
import com.xunk.equipment.mapper.BindExternTokenMapper;
import com.xunk.equipment.mapper.EquipmentMapper;
import com.xunk.equipment.mapper.HomeMapper;
import com.xunk.equipment.service.BaseService;
import com.xunk.equipment.service.zhijia.constant.ZhijiaConstant;
import com.xunk.equipment.service.zhijia.util.ZhijiaUtil;
import com.xunk.equipment.service.zhijia.vo.DeviceControlVO;
import com.xunk.equipment.service.zhijia.vo.DeviceInfoVO;
import com.xunk.equipment.service.zhijia.vo.WebSocketAck;
import com.xunk.equipment.websocket.WebsocketManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @ProjectName: xunk
 * @Package: com.xunk.equipment.service.zhijia.service
 * @ClassName: ZhijiaService
 * @Author: luanzhiwei
 * @Description: 智家
 * @Date: 2021/4/6 14:06
 * @Version: 1.0
 */
@Service
@Slf4j
public class ZhijiaService {


    @Autowired
    private ZhijiaConfig zhijiaConfig;
    @Autowired
    private BindExternTokenMapper bindExternTokenMapper;
    @Autowired
    private EquipmentMapper equipmentMapper;
    @Autowired
    private HomeMapper homeMapper;
    @Autowired
    private BaseService baseService;

    @Autowired
    private RedisService redisService;


    /**
     * 登录回调，触发获取智家token
     *
     * @param code
     * @param state
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult loginCallback(String code, String state) {
        log.info("智家登录回调接口");
        String localToken = redisService.getCacheMapValue(ZhijiaConstant.CALLBACK_STATE_KEY + state, ZhijiaConstant.ACCESS_TOKEN);
        String userId = Convert.toStr(redisService.getCacheMapValue(ZhijiaConstant.CALLBACK_STATE_KEY + state, ZhijiaConstant.USER_ID));
        log.info("本地token：{}", localToken);
        if (StringUtils.isEmpty(localToken)) {
            log.info("未认证的回调");
            return AjaxResult.success();
        }
        //认证成功，删除该state码以及我方userid和token
        redisService.deleteObject(ZhijiaConstant.CALLBACK_STATE_KEY + state);
        //开始获取token请求体
        Map<String, Object> body = new HashMap<>();
        //Oauth登录返回的code ，使用授权码换取token，code有效期为 10 分钟，只能使用1次
        body.put("code", code);
        //和调用登录h5 时使用的回调地址一致
        body.put("redirectUrl", zhijiaConfig.getRedirectUrl());
        //请求头
        Map<String, Object> headers = oauthHeaders(body, zhijiaConfig.getTokenUrl());
        //获取token
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getTokenUrl(), body, headers, JSONObject.class);
        log.info("获取智家token{}", jsonObject);
        //存储并绑定token
        if (!this.isSuccess(jsonObject)) {
            return AjaxResult.error(jsonObject.getString(ZhijiaConstant.RET_INFO));
        }
        this.cacheToken(jsonObject, userId);
        return AjaxResult.success();
    }

    /**
     * 缓存智家token和refreshtoken，智家的refreshToken始终有效,存入数据库，与当前用户userid绑定
     * 返回accessToken
     *
     * @param jsonObject
     * @param master
     * @return
     */
    private String cacheToken(JSONObject jsonObject, String master) {
        log.info("缓存智家refreshToken");
        JSONObject payload = jsonObject.getJSONObject(ZhijiaConstant.PAYLOAD);
        String accessToken = payload.getString(ZhijiaConstant.ACCESS_TOKEN);
        //expiresIn 秒为单位
        Integer expiresIn = Convert.toInt(payload.get(ZhijiaConstant.EXPIRES_IN));
        String refreshToken = payload.getString(ZhijiaConstant.REFRESH_TOKEN);
        redisService.setCacheObject(ZhijiaConstant.ACCESS_TOKEN_KEY + master, accessToken, expiresIn, TimeUnit.SECONDS);
        redisService.setCacheObject(ZhijiaConstant.REFRESH_TOKEN_KEY + master, refreshToken, 10, TimeUnit.HOURS);
        //userId与refreshToken在数据库绑定
        BindExternToken bindExternToken = bindExternTokenMapper.selectBindExternTokenByUserId(Convert.toLong(master));
        if (ObjectUtils.isEmpty(bindExternToken)) {
            log.info("不存在userId与token绑定关系");
            bindExternToken = new BindExternToken();
            bindExternToken.setRefreshToken(refreshToken);
            bindExternToken.setUserId(Convert.toLong(master));
            bindExternToken.setCreateBy(master);
            bindExternToken.setAccessToken(accessToken);
            bindExternTokenMapper.insertBindExternToken(bindExternToken);
        } else {
            log.info("存在userId与token绑定关系");
            bindExternToken.setRefreshToken(refreshToken);
            bindExternToken.setUpdateBy(master);
            bindExternToken.setAccessToken(accessToken);
            bindExternTokenMapper.updateBindExternToken(bindExternToken);
        }
        return accessToken;
    }


    /**
     * 获取用户登录url
     *
     * @return
     */
    public AjaxResult getLoginUrl(Long homeId, String localToken) {
        final Long userId = SecurityUtils.getUserId();
        final int count = homeMapper.selectCountByHomeIdAndUserId(homeId, Convert.toLong(userId));
        if (!(count == 1)) {
            log.info("用户{} 与家庭{} 不存在绑定关系", userId, homeId);
            return AjaxResult.error("用户与家庭不存在绑定关系");
        }
        final Long master = homeMapper.findMasterByHomeId(homeId);
        if (!ObjectUtil.equals(master, userId)) {
            //非户主，不允许授权
            return AjaxResult.error("请使用户主账号进行授权");
        }
        String uuid = RandomUtil.randomString(30);
        //根据生成的uuid缓存我方token和userId，便于后续验证uuid和获取我方token
        redisService.setCacheMapValue(ZhijiaConstant.CALLBACK_STATE_KEY + uuid, ZhijiaConstant.ACCESS_TOKEN, localToken);
        redisService.setCacheMapValue(ZhijiaConstant.CALLBACK_STATE_KEY + uuid, ZhijiaConstant.USER_ID, userId);
        redisService.expire(ZhijiaConstant.CALLBACK_STATE_KEY + uuid, 10, TimeUnit.MINUTES);
        StringBuffer sbf = new StringBuffer();
        // 测试环境 ：https://testaccount.haier.com/oauth/authorize?
        sbf.append(zhijiaConfig.getLoginUrl());
        //client_id值为海极网分配的systemid
        sbf.append("client_id=").append(zhijiaConfig.getSystemId());
        // response_type为授权方式, 这里固定为 code
        sbf.append("&response_type=code");
        // state为 应用 生成的随机字符, 在 用户 授权 回调时会原样返回给 应 用, 藉此可以判断来自 本平台 的回跳是否被伪造; 此参数非必传, 但推荐传送以增强安全性
        sbf.append("&state=").append(uuid);
        // redirect_uri指定回跳地址
        sbf.append("&redirect_uri=").append(zhijiaConfig.getRedirectUrl());
        //请求终端唯一标识，随机字符，最大长度不超过32位。在访问IoT设备中心绑定控制设备接口时强制校验终端标识ID是否和token匹配（注明：设备中心接口参数定义为 clientId）
        sbf.append("&multiportflag=").append(zhijiaConfig.getClientId());
        Map<String, Object> result = new HashMap<>();
        result.put("loginUrl", sbf.toString());
        return AjaxResult.success(result);
    }


    /**
     * 获取认证服务的请求头
     * systemId	String	Header	是	海极网申请的systemId
     * sequenceId	String	Header	否	报文流水(客户端唯一)客户端交易流水号。6-32 位。由客户端自行定义，自行生成。建议使用日期+顺序编号的方式。
     * apiVersion	String	Header	是	此处默认填v1
     * sign	String	Header	是	签名
     * timestamp	long	Header	是	Unix时间戳，精确到毫秒。
     * Content-Type	String	Header	是	application/json;charset=UTF-8
     *
     * @return
     */
    private Map<String, Object> oauthHeaders(Map body, String url) {
        Map<String, Object> headers = new HashMap();
        headers.put(ZhijiaConstant.SYSTEM_ID, zhijiaConfig.getSystemId());
        headers.put("sequenceId", StringUtils.EMPTY);
        //apiVersion 此处默认填v1
        headers.put(ZhijiaConstant.API_VERSION, "v1");
        String time = Convert.toStr(System.currentTimeMillis());
        try {
            headers.put(ZhijiaConstant.SIGN, ZhijiaUtil.signHttp(zhijiaConfig.getSystemId(), zhijiaConfig.getSystemKey(), time, JsonSerializer.serialize(body), new URL(url).getPath()));
        } catch (MalformedURLException e) {
            log.error("生成智家签名时，解析请求智家URL报错: {}", e.getMessage());
            throw new CustomException("获取认证服务的请求头异常");
        }
        headers.put(ZhijiaConstant.TIMESTAMP, time);
        headers.put("clientId", zhijiaConfig.getClientId());
        headers.put("Content-Type", "application/json;charset=UTF-8");
        log.info("oauthHeaders->{}", JsonSerializer.serialize(headers));
        return headers;
    }

    /**
     * 获取服务接口的公共请求头
     *
     * @param accessToken
     * @param body
     * @param urlPath，只传除去域名端口的后半截
     * @return
     */
    private Map<String, Object> getCommonHeaders(String accessToken, Map body, String urlPath) {
        log.info("拼接服务请求公共请求头");
        Map<String, Object> headers = new HashMap();
        //必填（登录后不为空，登录前可为空）	安全令牌 token，30 位字符。 用户登录 Haier U+ 云平台,由系统创建。用户退出 Haier U+ 云平台,由系统销毁。未登录时，访问不需要登录的平台接口，仍然需要传入本参数，参数值可为空或任意值（不超过30字符）
        headers.put(ZhijiaConstant.SYSTEM_ID, zhijiaConfig.getSystemId());
        //应用版本32 位字符,Haier uHome 云平台全局唯一。
        headers.put("appVersion", "1.0.1");
        //客户端ID27 位字符,客户端机编码与客户端 MAC 地址 拼合成唯一的客户端标识。
        // 主要用途为唯一标识客户端 (例如,手机)。手机机编码为 IMEI 码。 手机 MAC 为 12 位地址。
        // 命名规范:客户端机编码(15 位)-客户 端 MAC 地址(12 位)格式: XXXXXXXXXXXXXXX-XXXXXXXXXXXX
        // 举例: 356877020056553-08002700DC94。APP端可调用usdk获取，其他服务端自定义标识，不能为空。
        //此处传海极网分配的clientId
        headers.put("clientId", zhijiaConfig.getClientId());
        //报文流水(客户端唯一)客户端交易流水号。20 位, 前 14 位时间戳（格式：yyyyMMddHHmmss）,后 6 位流水 号。交易发生时,根据交易 笔数自增量。App应用访问uws接口时必须确保每次请求唯一，不能重复。
        headers.put("sequenceId", RandomUtil.randomString(30));
        headers.put(ZhijiaConstant.ACCESS_TOKEN, accessToken);
        String time = Convert.toStr(System.currentTimeMillis());
        //对请求进行签名运算产生的签名,签名算法见附录。body为空时，传空字符串
        String sign = ZhijiaUtil.signHttp(zhijiaConfig.getSystemId(), zhijiaConfig.getSystemKey(), time, MapUtils.isEmpty(body) ? "" : JsonSerializer.serialize(body), urlPath);
        headers.put("sign", sign);
        //应传入用户所在地时间戳，long型时间戳,精确到毫秒
        headers.put("timestamp", time);
        //该参数为多语言版提供支持。默认填写zh-cn即可。
        headers.put("language", "zh-cn");
        //代表客户端使用的时区。传入用户所在时区ID，国内服务请填写"Asia/Shanghai"即可, +8
        headers.put("timezone", "Asia/Shanghai");
        //也就是systemKey
        headers.put("appKey", zhijiaConfig.getSystemKey());
        //互联网媒体信息，填写为"application/json;charset=UTF-8"
        headers.put("Content-Type", "application/json;charset=UTF-8");
        headers.put("apiVersion", "v1");
        log.info("getCommonHeaders->{}", JsonSerializer.serialize(headers));
        return headers;
    }

    /**
     * 判断是否已授权
     * 根据我方token查询redis是否存在智家token，若存在，返回true
     * 否则根据userId从数据库查询是否存在智家refreshToken，若不存在，返回false
     * 若存在智家refreshToken，调用智家刷新token，并重新保存token到redis里，然后返回true
     *
     * @param userId
     * @param homeId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult authStatus(Long homeId, String userId) {
        final int count = homeMapper.selectCountByHomeIdAndUserId(homeId, Convert.toLong(userId));
        if (!(count == 1)) {
            return AjaxResult.error("用户与家庭不存在绑定关系");
        }
        final Long master = homeMapper.findMasterByHomeId(homeId);
        String zhijiaToken = redisService.getCacheObject(ZhijiaConstant.ACCESS_TOKEN_KEY + master);
        Map<String, Object> result = new HashMap<>();
        if (StringUtils.isNotEmpty(zhijiaToken)) {
            result.put("authStatus", true);
            return AjaxResult.success(result);
        }
        //根据userId从数据库查询refreshToken，若不存在，需要登录
        String refreshToken = redisService.getCacheObject(ZhijiaConstant.REFRESH_TOKEN_KEY + master);
        if (StringUtils.isEmpty(refreshToken)) {
            refreshToken = bindExternTokenMapper.selectRefreshTokenByUserId(Convert.toLong(userId));
        }
        if (StringUtils.isEmpty(refreshToken)) {
            result.put("authStatus", false);
            return AjaxResult.success(result);
        }
        refreshToken(refreshToken, userId);
        result.put("authStatus", true);

        if (!ObjectUtil.equals(master, userId)) {
            //非户主不允许授权
            return AjaxResult.error("请使用户主账号进行授权");
        } else {
            return AjaxResult.success(result);
        }
    }

    /**
     * 刷新token，返回accessToken
     *
     * @param refreshToken
     * @param master
     */
    private String refreshToken(String refreshToken, String master) {
        Map<String, Object> body = new HashMap<>();
        body.put("refreshToken", refreshToken);
        Map<String, Object> headers = oauthHeaders(body, zhijiaConfig.getRefreshTokenUrl());
        //获取token
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getRefreshTokenUrl(), body, headers, JSONObject.class);
        if (!this.isSuccess(jsonObject)) {
            throw new CustomException("获取token失败");
        }
        return this.cacheToken(jsonObject, master);
    }

    /**
     * 判断是否成功
     *
     * @param jsonObject
     * @return
     */
    private boolean isSuccess(JSONObject jsonObject) {
        if (ZhijiaConstant.SUCCESS_CODE.equals(Convert.toStr(jsonObject.get(ZhijiaConstant.RET_CODE)))) {
            return true;
        }
        return false;
    }

    /**
     * 授权后的流程，可调用该方法获取智家token
     *
     * @param homeId
     * @return
     */
    private String getZhijiaToken(Long homeId) {
        final Long master = homeMapper.findMasterByHomeId(homeId);
        String accessToken = redisService.getCacheObject(ZhijiaConstant.ACCESS_TOKEN_KEY + master);
        if (StringUtils.isNotEmpty(accessToken)) {
            return accessToken;
        }
        //根据户主userId从数据库查询refreshToken，若不存在，需要登录
        String refreshToken = redisService.getCacheObject(ZhijiaConstant.REFRESH_TOKEN_KEY + master);
        if (StringUtils.isEmpty(refreshToken)) {
            refreshToken = bindExternTokenMapper.selectRefreshTokenByUserId(Convert.toLong(master));
        }
        if (StringUtils.isEmpty(refreshToken)) {
            log.info("用户未授权智家");
            throw new CustomException("请先登录授权");
        }
        redisService.setCacheObject(ZhijiaConstant.REFRESH_TOKEN_KEY + master, refreshToken, 10, TimeUnit.HOURS);
        return refreshToken(refreshToken, Convert.toStr(master));
    }

    /**
     * 刷新设备列表接口
     * 可用于获取用户全部设备
     *
     * @param homeId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult deviceInfos(Long homeId) {
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getUwsPrefix() + ZhijiaConstant.URL_ALL_DEVICE_INFO, StringUtils.EMPTY, getCommonHeaders(getZhijiaToken(homeId), MapUtils.EMPTY_MAP, ZhijiaConstant.URL_ALL_DEVICE_INFO), JSONObject.class);
        if (!this.isSuccess(jsonObject)) {
            return AjaxResult.error("设备刷新失败");
        }
        JSONArray jsonArray = jsonObject.getJSONArray(ZhijiaConstant.PAYLOAD);
        List<DeviceInfoVO> deviceInfoVOS = JsonSerializer.deserializeArray(JsonSerializer.serialize(jsonArray), DeviceInfoVO.class);
        final List<DeviceInfoVO> collect = deviceInfoVOS.stream().filter(item -> item.isVSC()).collect(Collectors.toList());
        //删除设备影子
        final Long deptId = homeMapper.findDeptIdByHomeId(homeId);
        final List<String> deviceSns = equipmentMapper.selectDeviceSnBySupplierAndDeptId(SupplierEnum.UPLUS.getSupplierName(), deptId);
        for (String deviceSn : deviceSns) {
            String ancestor = baseService.findAncestorByDeptId(deptId);
            redisService.deleteObject(ancestor.concat(":").concat(deviceSn));
        }
        //保存最新设备
        for (DeviceInfoVO deviceInfoVO : collect) {
            //TODO 无法区分家庭权限
            final EquipmentDO equipmentDO = deviceInfoVO.extract();
            final String deviceId = deviceInfoVO.getDeviceId();
            boolean equalFlag = false;
            for (String deviceSn : deviceSns) {
                if (StringUtils.equals(deviceId, deviceSn)) {
                    equalFlag = true;
                    break;
                }
            }
            if (!equalFlag) {
                //不存在代表是新设备
                final EquipmentBaseInfoDO deviceBaseInfo = equipmentMapper.getDeviceBaseInfo(deviceInfoVO.getDeviceType(), SupplierEnum.UPLUS.getSupplierName());
                if (!ObjectUtil.isEmpty(deviceBaseInfo)) {
                    equipmentDO.setDeviceName(deviceBaseInfo.getBaseName());
                    equipmentDO.setDeviceType(deviceBaseInfo.getBaseTypeCode());
                    equipmentDO.setDeptId(deptId);
                    equipmentDO.setDeviceBaseId(deviceBaseInfo.getId());
                    equipmentDO.setDeviceCategory(deviceBaseInfo.getBaseCategory());
                    equipmentDO.setDeviceSource(deviceBaseInfo.getBaseSource());
                    equipmentMapper.insertEquipment(equipmentDO);
                } else {
                    log.info("设备id：{}，没有在sys_device_base_info中维护基础数据", deviceId);
                    //TODO 不落库，没有维护数据，无法找到对应的service处理设备控制等功能
                }
            }
        }
        //删除已不存在的设备
        for (String deviceSn : deviceSns) {
            boolean nonexistentFlag = true;
            for (DeviceInfoVO deviceInfoVO : collect) {
                final String deviceId = deviceInfoVO.getDeviceId();
                if (StringUtils.equals(deviceSn, deviceId)) {
                    nonexistentFlag = false;
                    break;
                }
            }
            if (nonexistentFlag) {
                equipmentMapper.deleteEquipment(deviceSn, null);
            }
        }
        //保存设备
        return AjaxResult.success(jsonArray);
    }

    /**
     * 查询设备详情接口
     *
     * @param deviceSn
     * @return
     */
    public AjaxResult deviceDetail(Long homeId, String deviceSn) {
        Map<String, Object> body = new HashMap<>();
        body.put(ZhijiaConstant.DEVICE_ID, deviceSn);
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getUwsPrefix() + ZhijiaConstant.URL_DEVICE_DETAIL, body, getCommonHeaders(getZhijiaToken(homeId), body, ZhijiaConstant.URL_DEVICE_DETAIL), JSONObject.class);
        log.info("deviceDetail->{}", jsonObject.toJSONString());
        if (!this.isSuccess(jsonObject)) {
            return AjaxResult.error("查询设备详情失败");
        }
        JSONObject payload = jsonObject.getJSONObject(ZhijiaConstant.PAYLOAD);
//        DeviceVersion deviceVersion = JsonSerializer.deserialize(JsonSerializer.serialize(payload), DeviceVersion.class);
        return AjaxResult.success(payload);
    }

    /**
     * 查询单个设备的状态
     *
     * @param deviceSn
     * @param part
     * @return
     */
    public AjaxResult deviceStatus(Long homeId, String deviceSn, String part) {
        Map<String, Object> body = new HashMap<>();
        body.put(ZhijiaConstant.DEVICE_ID, deviceSn);
        body.put(ZhijiaConstant.PART, StringUtils.isEmpty(part) ? "1" : part);
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getUwsPrefix() + ZhijiaConstant.URL_DEVICE_STATUS, body, getCommonHeaders(getZhijiaToken(homeId), body, ZhijiaConstant.URL_DEVICE_STATUS), JSONObject.class);
        log.info("deviceStatus->{}", jsonObject.toJSONString());
        if (!this.isSuccess(jsonObject)) {
            return AjaxResult.error("查询设备状态失败");
        }
        JSONObject payload = jsonObject.getJSONObject(ZhijiaConstant.PAYLOAD);
        return AjaxResult.success(payload);
    }

    /**
     * 控制设备
     *
     * @param equipmentDO 设备id
     * @param type        属性key
     * @param value       属性value
     * @return
     */
    public Boolean control(EquipmentDO equipmentDO, String type, String value) {
        final DeviceControlVO deviceControl = new DeviceControlVO();
        final String deviceSn = equipmentDO.getDeviceSn();
        final Long homeId = equipmentMapper.selectHomeIdByDeviceSn(deviceSn);
        deviceControl.setCallbackUrl(StringUtils.EMPTY);
        deviceControl.setDeviceId(deviceSn);
        Map<String, String> cmdArgs = new HashMap<>();
        cmdArgs.put(type, value);
        deviceControl.setCmdArgs(cmdArgs);
        deviceControl.setCallbackUrl("http://120.27.27.46:21000/equipment/uplus/notify?uuid=123");
        deviceControl.setCmdName(StringUtils.EMPTY);
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getUwsPrefix() + ZhijiaConstant.URL_DEVICE_CMD, JsonSerializer.deserializeMap(JsonSerializer.serialize(deviceControl)), getCommonHeaders(getZhijiaToken(homeId), JsonSerializer.deserializeMap(JsonSerializer.serialize(deviceControl)), ZhijiaConstant.URL_DEVICE_CMD), JSONObject.class);
        if (!this.isSuccess(jsonObject)) {
            log.info("返回失败 {}", jsonObject.toJSONString());
            return false;
        }
        return true;
    }

    public Boolean control(EquipmentDO equipmentDO, List<String> types, List<String> values) {
        final DeviceControlVO deviceControl = new DeviceControlVO();
        final String deviceSn = equipmentDO.getDeviceSn();
        final Long homeId = equipmentMapper.selectHomeIdByDeviceSn(deviceSn);
        deviceControl.setCallbackUrl(StringUtils.EMPTY);
        deviceControl.setDeviceId(deviceSn);
        Map<String, String> cmdArgs = new HashMap<>();
        int length = types.size();
        for (int i = 0; i < length; i++) {
            cmdArgs.put(types.get(i), values.get(i));
        }
        deviceControl.setCmdArgs(cmdArgs);
        deviceControl.setCmdName(IdUtils.fastUUID());
        log.info("U+ 组合命令 {}", JSON.toJSONString(deviceControl));
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getUwsPrefix() + ZhijiaConstant.URL_DEVICE_CMD, JsonSerializer.deserializeMap(JsonSerializer.serialize(deviceControl)), getCommonHeaders(getZhijiaToken(homeId), JsonSerializer.deserializeMap(JsonSerializer.serialize(deviceControl)), ZhijiaConstant.URL_DEVICE_CMD), JSONObject.class);
        if (!this.isSuccess(jsonObject)) {
            log.info("返回失败");
            return false;
        }
        return true;
    }

    /**
     * 查询第三方开通预授权的型号列表
     *
     * @param homeId
     * @return
     */
    public AjaxResult deviceModels(Long homeId) {
        JSONObject jsonObject = HttpUtil.post(zhijiaConfig.getUwsPrefix() + ZhijiaConstant.URL_DEVICE_MODELS, StringUtils.EMPTY, getCommonHeaders(getZhijiaToken(homeId), MapUtils.EMPTY_MAP, ZhijiaConstant.URL_DEVICE_MODELS), JSONObject.class);
        log.info("deviceModels->{}", jsonObject);
        if (this.isSuccess(jsonObject)) {
            return AjaxResult.success(jsonObject.getJSONArray(ZhijiaConstant.PAYLOAD));
        }
        return AjaxResult.error(jsonObject.getString(ZhijiaConstant.RET_INFO));
    }

    /**
     * 处理智家消息推送
     *
     * @param message
     */
    public void processMessage(String message) {
        /* 报文格式
        {
	"body": {
		"timestamp": "1623828170540",
		"category": "Dev",
		"sn": "4616f493968e49308f7436dd00087833",
		"wifiType": "201c80c70c50031c1011fbafa0d9bb000000251bf44f972a7d075fb464b93840",
		"args": "",
		"deviceType": "1001101P",
		"name": "online",
		"sourceId": "D003EBDCC04E010A0211"
	},
	"header": {
		"topic": "DEV_EVENT",
		"qos": 0,
		"sn": "4616f493968e49308f7436dd00087833",
		"ts": 1623828170574,
		"keys": {
			"typeId": "201c80c70c50031c1011fbafa0d9bb000000251bf44f972a7d075fb464b93840",
			"deviceId": "D003EBDCC04E010A0211"
		},
		"ver": "v1.0.1",
		"from": "haier",
		"type": "online",
		"snd": "m2m"
	}
}
        * */
        try {
            log.info("开始处理智家设备消息推送");
            final Map<String, Object> receive = JsonSerializer.deserializeMap(message);
            final Map head = (Map) receive.get("header");
            final Map body = (Map) receive.get("body");
            final String type = Convert.toStr(head.get("type"));
            final Map keys = (Map) head.get("keys");
            final String deviceSn = Convert.toStr(keys.get("deviceId"));
            //deptIdStr = deptId:homeId
            String deptIdStr = redisService.getCacheMapValue(ZhijiaConstant.DEVICE_SN_DEPT_ID_HOME_ID, deviceSn);
            if (StringUtils.isEmpty(deptIdStr)) {
                log.info("设备号{}不存在，不处理", deviceSn);
                return;
            }
            if (StringUtils.equals(type, "online")) {
                equipmentMapper.updateEquipmentOnline(deviceSn, 1L);
            } else if (StringUtils.equals(type, "offline")) {
                equipmentMapper.updateEquipmentOnline(deviceSn, 0L);
            } else {
                final String[] split = deptIdStr.split(":");
                final Long deptId = Convert.toLong(split[0]);
                final Long homeId = Convert.toLong(split[1]);
                Map<String, Object> cacheMap = this.getShadowByDeviceSn(homeId, deptId, deviceSn);
                String ancestor = baseService.findAncestorByDeptId(deptId);
                String key = ancestor.concat(":").concat(deviceSn);
                final Object args = body.get("args");
                if (ObjectUtil.isNotEmpty(args)) {
                    cacheMap.put("reported", args);
                    redisService.setCacheMap(key, cacheMap);
                }
            }
        } catch (Exception e) {
            log.info("处理智家设备消息推送发生异常：{}", e.getMessage());
        }
    }

    /**
     * 处理系统应答消息
     *
     * @param message
     */
    public void processAck(String message) {
        try {
            WebSocketAck response = JsonSerializer.deserialize(message, WebSocketAck.class);
            String cmd = response.getCmd();
            String code = response.getData().getCode();
            switch (cmd) {
                case "authenticate-ack":
                    if (ZhijiaConstant.SUCCESS_CODE.equals(code)) {
                        log.info("连接智家websocket服务端成功！");
                    } else {
                        log.info("连接智家websocket服务端失败！");
                        // 一些处理，例如短信告警等
                    }
                    break;
            }
        } catch (Exception e) {
            log.error("处理应答信息出现异常：{}", e);
        }
    }

    /**
     * 获取设备影子
     *
     * @param homeId
     * @param deviceSn
     */
    public Map getShadow(Long homeId, String deviceSn, String key) {
        //设备状态，主要是设备属性
        final AjaxResult status = this.deviceStatus(homeId, deviceSn, "1");
        final Object data = status.getData();
        final Map<String, Object> cache = JsonSerializer.deserializeMap(JsonSerializer.serialize(data));
        //设备详情，主要是硬件信息
        final AjaxResult detail = this.deviceDetail(homeId, deviceSn);
        cache.putAll(JsonSerializer.deserializeMap(JsonSerializer.serialize(detail.getData())));
        redisService.setCacheMap(key, cache);
        return cache;
    }

    public Map<String, Object> getShadowByDeviceSn(Long homeId, Long deptId, String deviceSn) {
        String ancestor = baseService.findAncestorByDeptId(deptId);
        String key = ancestor.concat(":").concat(deviceSn);
        Map<String, Object> cacheMap = redisService.getCacheMap(key);
        // TODO 这里暂时不从缓存里取，不管怎么样，都去查询。等socket可以推送了在修改此处
//        if (DataUtils.isEmpty(cacheMap)) {
        try {
            log.info("直接查询U+设备信息");
            cacheMap = getShadow(homeId, deviceSn, key);
        } catch (Exception e) {
            log.error("getShadowByDeviceSn查询设备影子异常", e);
        }
//        }
        return cacheMap;
    }

    public Map resolveUhomeDeviceStatus(Long homeId, EquipmentDO equipmentDO, Map<String, String> properties, Map<String, Object> cacheMap) {
        String deviceSn = equipmentDO.getDeviceSn();
        Long deptId = equipmentDO.getDeptId();
//        Map<String, Object> cacheMap = getShadowByDeviceSn(homeId, deptId, deviceSn);
        Map reported = (Map) cacheMap.get("reported");
        properties.entrySet().forEach(entry -> {
            reported.put(entry.getKey(), entry.getValue());
        });
        cacheMap.put("reported", reported);
        String ancestor = baseService.findAncestorByDeptId(deptId);
        String key = ancestor.concat(":").concat(deviceSn);
        redisService.setCacheMap(key, cacheMap);
        return reported;
    }
}
